package com.rankeiot.core.excel;

import ch.rabanti.nanoxlsx4j.Helper;
import ch.rabanti.nanoxlsx4j.ImportOptions;

import ch.rabanti.nanoxlsx4j.exceptions.IOException;
import ch.rabanti.nanoxlsx4j.lowLevel.*;
import ch.rabanti.nanoxlsx4j.styles.StyleRepository;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Map;
import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

/**
 * Excel 大文件读取
 */
public class ExcelLoader {
    public static void load(InputStream in, Consumer<Row> rowHandler){
        ExcelLoader el=new ExcelLoader(in);
        try {
            el.read(rowHandler);
        } catch (IOException|java.io.IOException e) {
            throw new RuntimeException(e);
        }
    }
    private String filePath;
    private InputStream inputStream;
    private ByteArrayInputStream memoryStream;

    private WorkbookReader workbook;
    private MetaDataReader metaDataReader;
    private final ImportOptions importOptions;
    private StyleReaderContainer styleReaderContainer;

    /**
     * Constructor with stream and import options as parameter
     *
     * @param stream
     *            Stream of the XLSX file to load
     * @param options
     *            Import options to override the automatic approach of the reader.
     *            See {@link ImportOptions} for information about import options
     */
    public ExcelLoader(InputStream stream, ImportOptions options) {

        this.inputStream = stream;
        this.importOptions = options;
    }
    public ExcelLoader(InputStream stream) {

        this.inputStream = stream;
        this.importOptions = new ImportOptions();
    }
    /**
     * Constructor with file path and import options as parameter
     *
     * @param path
     *            File path of the XLSX file to load
     * @param options
     *            Import options to override the automatic approach of the reader.
     *            See {@link ImportOptions} for information about import options
     */
    public ExcelLoader(String path, ImportOptions options) {
        this.filePath = path;
        this.importOptions = options;
    }

    /**
     * Gets the input stream of the specified file in the archive (XLSX file)
     *
     * @param name
     *            Name of the XML file within the XLSX file
     * @param file
     *            Zip file (XLSX)
     * @return InputStream of the specified file
     * @throws IOException
     *             Throws IOException in case of an error
     */
    private InputStream getEntryStream(String name, ZipFile file) throws IOException {
        InputStream is = null;

        try {
            ZipEntry comparison;
            if (file != null) {
                comparison = file.getEntry(name);
                is = file.getInputStream(comparison);
            }
            else {
                memoryStream.reset();
                ZipInputStream zs = new ZipInputStream(memoryStream);
                while ((comparison = zs.getNextEntry()) != null) {
                    if (comparison.getName().equals(name)) {
                        is = zs;
                        break;
                    }
                }
            }
            if (is == null) {
                throw new IOException("The entry '" + name + "' is missing in the file");
            }
            return is;
        }
        catch (Exception ex) {
            throw new IOException("There was an error while extracting a stream from a XLSX file. Please see the inner exception:", ex);
        }
    }

    /**
     * Reads the XLSX file from a file path or a file stream
     *
     * @throws IOException
     *             Throws IOException in case of an error
     */
    public void read(Consumer<Row> rowHandler) throws IOException,java.io.IOException {
        ZipFile zf = null;
        try {
            if (inputStream == null && !Helper.isNullOrEmpty(filePath)) {
                zf = new ZipFile(this.filePath);
            }
            else if (inputStream != null) {
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024];
                for (int i = inputStream.read(buffer); i != -1; i = inputStream.read(buffer)) {
                    os.write(buffer, 0, i);
                }
                inputStream.close();
                memoryStream = new ByteArrayInputStream(os.toByteArray());
            }
            else {
                throw new IOException("No valid stream or file path was provided to open");
            }
            InputStream stream;
            SharedStringsReader sharedStrings = new SharedStringsReader(importOptions);
            stream = getEntryStream("xl/sharedStrings.xml", zf);
            sharedStrings.read(stream);

            StyleRepository.getInstance().setImportInProgress(true);
            StyleReader styleReader = new StyleReader();
            stream = getEntryStream("xl/styles.xml", zf);
            styleReader.read(stream);
            this.styleReaderContainer = styleReader.getStyleReaderContainer();
            StyleRepository.getInstance().setImportInProgress(false);

            this.workbook = new WorkbookReader();
            stream = getEntryStream("xl/workbook.xml", zf);
            this.workbook.read(stream);

            metaDataReader = new MetaDataReader();
            stream = getEntryStream("docProps/app.xml", zf);
            this.metaDataReader.readAppData(stream);
            stream = getEntryStream("docProps/core.xml", zf);
            this.metaDataReader.readCoreData(stream);

            int worksheetIndex = 1;
            WorksheetDataReader wr;
            String nameTemplate = "sheet" + worksheetIndex + ".xml";
            String name = "xl/worksheets/" + nameTemplate;
            for (Map.Entry<Integer, WorkbookReader.WorksheetDefinition> definition : workbook.getWorksheetDefinitions().entrySet()) {
                stream = getEntryStream(name, zf);
                wr = new WorksheetDataReader(sharedStrings, styleReaderContainer, importOptions);
                wr.on(rowHandler);
                wr.read(stream);
                //   this.worksheets.put(definition.getKey(), wr);
                worksheetIndex++;
                nameTemplate = "sheet" + worksheetIndex + ".xml";
                name = "xl/worksheets/" + nameTemplate;

                break;
            }
        }
        catch (Exception ex) {
            throw new IOException("There was an error while reading an XLSX file. Please see the inner exception:", ex);
        }
        finally {
            if (memoryStream != null) {
                memoryStream.close();
            }
        }
    }
}
